<!DOCTYPE html>
<html lang="en">
<head>
    
    <meta charset="utf-8">
    <title>engines/generalEngine.js - Documentation</title>
    
    
    <script src="scripts/prettify/prettify.js"></script>
    <script src="scripts/prettify/lang-css.js"></script>
    <!--[if lt IE 9]>
      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <link type="text/css" rel="stylesheet" href="styles/prettify.css">
    <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
    <script src="scripts/nav.js" defer></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
  <div class="navicon"></div>
</label>

<label for="nav-trigger" class="overlay"></label>

<nav >
    
    <input type="text" id="nav-search" placeholder="Search" />
    
    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="openBSE.BrowserNotSupportError.html">BrowserNotSupportError</a></li><li><a href="openBSE.Contextmenu.html">Contextmenu</a><ul class='methods'><li data-type='method' style='display: none;'><a href="openBSE.Contextmenu.html#closeContextmenu">closeContextmenu</a></li><li data-type='method' style='display: none;'><a href="openBSE.Contextmenu.html#getBulletScreenEvent">getBulletScreenEvent</a></li><li data-type='method' style='display: none;'><a href="openBSE.Contextmenu.html#getContextmenuState">getContextmenuState</a></li></ul></li><li><a href="openBSE.GeneralEngine.html">GeneralEngine</a><ul class='methods'><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#add">add</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#bind">bind</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#cleanBuffer">cleanBuffer</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#cleanScreen">cleanScreen</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#pause">pause</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#play">play</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#playAllBulletScreens">playAllBulletScreens</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#stop">stop</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#unbind">unbind</a></li></ul></li></ul><h3>Events</h3><ul><li><a href="openBSE.GeneralEngine.html#event:click">click</a></li><li><a href="openBSE.GeneralEngine.html#event:contextmenu">contextmenu</a></li><li><a href="openBSE.GeneralEngine.html#event:mouseenter">mouseenter</a></li><li><a href="openBSE.GeneralEngine.html#event:mouseleave">mouseleave</a></li></ul><h3>Namespaces</h3><ul><li><a href="openBSE.html">openBSE</a><ul class='methods'><li data-type='method' style='display: none;'><a href="openBSE.html#.getVersion">getVersion</a></li></ul></li></ul>
</nav>

<div id="main">
    
    <h1 class="page-title">engines/generalEngine.js</h1>
    

    



    
    <section>
        <article>
            <pre class="prettyprint source linenums"><code>import LinkedList from '../lib/linkedList'
import Event from '../lib/event'
import RenderersFactory from '../renderers/renderersFactory'
import GeneralType from './generalType'
import Helper from '../lib/helper'
import Resources from '../lib/resources'
import { requestAnimationFrame, cancelAnimationFrame } from '../lib/requestAnimationFrame'

/** 
 * 弹幕引擎对象类 
 * @alias openBSE.GeneralEngine
 * @property {openBSE~Options} options - 设置或获取全局选项。
 * @property {bool} visibility - 获取或设置弹幕可见性。
 * @property {string} renderMode - 获取渲染模式。取值为“canvas”、“css3”、“webgl”或“svg”。
 * @property {bool} playState - 获取播放状态。true：正在播放；false：已暂停/停止播放。
 * @property {openBSE~DebugInfo} debugInfo - 获取调试信息。
 * @throws {openBSE.BrowserNotSupportError} 浏览器不支持特定渲染模式时引发错误。
 * @throws {TypeError} 传入的参数错误时引发错误。请参阅 MDN [TypeError]{@link https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError} 。
 */
class GeneralEngine {
    /**
     * 创建一个弹幕引擎对象。
     * @param {Element} element - 要加载弹幕的元素：有关 Element 接口的信息请参阅MDN [Element]{@link https://developer.mozilla.org/zh-CN/docs/Web/API/Element} 。
     * @param {openBSE~generalOptions} [options] - 全局选项：一个 {@link openBSE~generalOptions} 结构。
     * @param {string} [renderMode="canvas"] - 渲染模式：默认为“canvas”, “可选css3”， “webgl”和“svg”。一般建议使用“canvas”（特别是 FireFox 浏览器 CSS3 渲染效率较低）。在一些版本较老的浏览器中“window.devicePixelRatio”变量不被支持或支持不完整，这会导致在高DPI和页面被缩放的情况下“canvas”和“webgl”渲染模式弹幕显示不正常的情况（弹幕模糊），此时建议使用“css3”渲染模式。
     */
    constructor(element, options, renderMode = 'canvas') {
        //变量初始化
        /**
         * 开始时间
         * @private @type {number}
         */
        let _startTime;
        /**
         * 暂停时间
         * @private @type {number}
         */
        let _pauseTime = 0;
        /**
         * 弹幕缓冲区
         * @private @type {LinkedList}
         */
        let _bulletScreenBuffer = new LinkedList();
        /**
         * 实时弹幕列表
         * @private @type {LinkedList}
         */
        let _realTimeBulletScreens = new LinkedList();
        /**
         * 延迟弹幕总数
         * @private @type {number}
         */
        let _delayBulletScreenCount = 0;
        /**
         * 延迟（单位：毫秒）
         * @private @type {number}
         */
        let _delay = 0;
        /**
         * 播放标志
         * @private @type {boolean}
         */
        let _playing;
        /**
         * requestAnimationFrame 句柄
         */
        let _requestAnimationFrameHandel = null;
        /**
         * 刷新频率
         * @private @type {number}
         */
        let _refreshRate = 0.06; //初始刷新频率
        /**
         * 上一次刷新时间
         * @private @type {number}
         */
        let _lastRefreshTime;
        /**
         * 全局选项
         * @private @type {openBSE~generalOptions}
         */
        let _options;
        /**
         * 默认全局变量
         * @private @readonly
         */
        const _defaultOptions = {
            /** 垂直间距 */
            verticalInterval: 8,
            /** 播放速度(倍数) */
            playSpeed: 1,
            /** 时间基准 */
            clock: time => new Date().getTime() - _startTime,
            /** 缩放比例 */
            scaling: 1,
            /** 超时丢弃 */
            timeOutDiscard: true,
            /** 要隐藏的弹幕类型 */
            hiddenTypes: 0,
            /** 弹幕不透明度 */
            opacity: 1,
            /** 鼠标经过样式 */
            cursorOnMouseOver: 'pointer',
            /** 默认弹幕样式 */
            defaultStyle: {
                /** 阴影的模糊级别，0为不显示阴影 */
                shadowBlur: 2,
                /** 字体粗细 */
                fontWeight: '600',
                /** 字体系列 */
                fontFamily: 'sans-serif',
                /** 字体大小（单位：像素） */
                size: 25,
                /** 外框颜色 */
                boxColor: null,
                /** 字体颜色 */
                color: 'white',
                /** 描边颜色 */
                borderColor: null,
                /** 弹幕速度（单位：像素/毫秒） 仅流弹幕类型有效 */
                speed: 0.15,
                /** 弹幕停留时间 仅固定弹幕类型有效 */
                residenceTime: 5000
            }
        }

        /**
         * 全局选项类型
         * @private @readonly
         */
        const _optionsType = {
            verticalInterval: 'number',
            playSpeed: 'number',
            clock: 'function',
            scaling: 'number',
            timeOutDiscard: 'boolean',
            hiddenTypes: 'number',
            opacity: 'number',
            cursorOnMouseOver: 'string',
            defaultStyle: {
                shadowBlur: 'number',
                fontWeight: ['string', 'number'],
                fontFamily: 'string',
                size: 'number',
                boxColor: ['string', 'null'],
                color: 'string',
                borderColor: ['string', 'null'],
                speed: 'number',
                residenceTime: 'number'
            }
        }

        /**
         * 默认弹幕数据
         * @private @readonly
         */
        const _defaultBulletScreen = {
            /** 弹幕文本 */
            text: null,
            /** 是否允许丢弃 */
            canDiscard: true,
            /** 弹幕进入时间 */
            startTime: null,
            /** 弹幕类型 */
            type: GeneralType.rightToLeft,
            /** 弹幕层级（越大越前） */
            layer: 0
        }

        /**
         * 弹幕数据类型
         * @private @readonly
         */
        const _bulletScreenType = {
            text: 'string',
            canDiscard: 'boolean',
            startTime: 'number',
            type: 'number',
            layer: 'number'
        }

        _options = Helper.setValues(options, _defaultOptions, _optionsType); //设置默认值

        //事件初始化
        let _event = new Event();
        /**
         * 弹幕单击事件。当单击弹幕时触发。
         * @event openBSE.GeneralEngine#click
         * @property {openBSE~GeneralEvent} e - 弹幕事件结构
         */
        _event.add('click');
        /**
         * 弹幕上下文菜单事件。当触发弹幕上下文菜单时触发。
         * @event openBSE.GeneralEngine#contextmenu
         * @property {openBSE~GeneralBulletScreenEvent} e - 弹幕事件结构
         */
        _event.add('contextmenu');
        /**
        * 弹幕鼠标离开事件。当鼠标离开弹幕时触发。
        * @event openBSE.GeneralEngine#mouseleave
        * @property {openBSE~GeneralBulletScreenEvent} e - 弹幕事件结构
        */
        _event.add('mouseleave');
        /**
         * 弹幕鼠标进入事件。当鼠标进入弹幕时触发。
         * @event openBSE.GeneralEngine#mouseenter
         * @property {openBSE~GeneralBulletScreenEvent} e - 弹幕事件结构
         */
        _event.add('mouseenter');
        /**
         * 绑定事件处理程序
         * @function
         * @description 绑定事件处理程序。当事件处理程序返回值为 false 时停止冒泡。
         * @param {string} name - 事件名称
         * @param {function} fun - 事件处理程序
         * @listens openBSE.GeneralEngine#click
         * @listens openBSE.GeneralEngine#contextmenu
         * @listens openBSE.GeneralEngine#mouseleave
         * @listens openBSE.GeneralEngine#mouseenter
         * @throws {TypeError} 传入的参数错误或事件不存在时引发错误。请参阅 MDN [TypeError]{@link https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError} 。
         */
        this.bind = _event.bind;
        /**
         * 解绑事件处理程序（fun为空解绑所有事件处理程序）
         * @function
         * @param {string} name - 事件名称
         * @param {function} fun - 事件处理程序
         * @throws {TypeError} 传入的参数错误或事件不存在时引发错误。请参阅 MDN [TypeError]{@link https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError} 。
         */
        this.unbind = _event.unbind;
        //初始化
        let _elementSize = {
            width: element.clientWidth / _options.scaling,
            height: element.clientHeight / _options.scaling
        }
        let _oldDevicePixelRatio = Helper.getDevicePixelRatio();
        let _oldScaling = _options.scaling;
        let _oldClientWidth = element.clientWidth;
        let _oldClientHeight = element.clientHeight;
        let _oldHiddenTypes = _options.hiddenTypes;
        let _oldOpacity = _options.opacity;
        //渲染器工厂
        let renderersFactory = new RenderersFactory(element, _options, _elementSize, bulletScreenEventTrigger);
        let _renderer = renderersFactory.getGeneralRenderer(renderMode); //实例化渲染器
        setInterval(setSize, 100);

        //公共函数
        /**
         * 设置或获取全局选项
         * @private
         **/
        Object.defineProperty(this, 'options', {
            get: function () {
                return Helper.clone(_options);
            },
            set: function (options) {
                _options = Helper.setValues(options, _options, _optionsType, false); //设置默认值
                if (_oldHiddenTypes != _options.hiddenTypes) {
                    _oldHiddenTypes = _options.hiddenTypes;
                    if (!_playing) _renderer.draw(); //非播放状态则重绘
                }
                if (_oldOpacity != _options.opacity) {
                    _oldOpacity = _options.opacity;
                    _renderer.setOpacity();
                }
            }
        });

        /**
         * 添加弹幕到弹幕列表。
         * @description 添加弹幕到弹幕列表。由于弹幕在屏幕上出现过后，弹幕引擎将从列表中彻底删除此弹幕。所以，在改变播放进度时，可能需要先[清空弹幕列表]{@link openBSE.BulletScreenEngine#cleanBulletScreenList}，然后重新加载此播放进度以后的弹幕。
         * @param {openBSE~GeneralBulletScreen} bulletScreen - 单条弹幕数据：一个 {@link openBSE~GeneralBulletScreen} 结构。
         * @throws {TypeError} 传入的参数错误时引发错误。请参阅 MDN [TypeError]{@link https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError} 。
         */
        this.add = function (bulletScreen) {
            _defaultBulletScreen.startTime = _options.clock();
            bulletScreen = Helper.setValues(bulletScreen, _defaultBulletScreen, _bulletScreenType); //设置默认值

            if (
                bulletScreen.type != GeneralType.leftToRight &amp;&amp;
                bulletScreen.type != GeneralType.rightToLeft &amp;&amp;
                bulletScreen.type != GeneralType.top &amp;&amp;
                bulletScreen.type != GeneralType.bottom
            ) throw new TypeError(Resources.PARAMETERS_TYPE_ERROR);

            Helper.checkTypes(bulletScreen.style, _optionsType.defaultStyle); //检查弹幕样式类型

            let newNode = new LinkedList.node(bulletScreen);
            _bulletScreenBuffer.forEach(function (node) {
                let lastBulletScreen = node.element;
                if (bulletScreen.startTime > lastBulletScreen.startTime) return {
                    add: { addToUp: true, node: newNode },
                    stop: true
                };
            }, true);
            if (newNode.linkedList === null) _bulletScreenBuffer.push(newNode, false);

        };

        /**
         * 开始播放弹幕。
         */
        this.play = function () {
            if (!_playing) {
                if (!_startTime)
                    _startTime = new Date().getTime();
                if (_pauseTime)
                    _startTime += _options.clock() - _pauseTime;
                _lastRefreshTime = null;
                _playing = true;
                _requestAnimationFrameHandel = requestAnimationFrame(refresh);
            }
        };

        /**
         * 继续所有在事件响应中设置了 e.pause = true; 弹幕的播放。
         */
        this.playAllBulletScreens = () =>
            _realTimeBulletScreens.forEach((node) => node.element.pause = false);

        /**
         * 暂停播放弹幕。
         * @description 暂停播放弹幕。暂停播放弹幕是指弹幕播放暂停，所有未出现的弹幕将停止出现，已出现的弹幕停止运动，停止消失。
         */
        this.pause = function () {
            if (_playing) {
                _pauseTime = _options.clock();
                _playing = false;
                cancelAnimationFrame(_requestAnimationFrameHandel);
            }
        };

        /**
         * 清空弹幕缓冲区。
         * @description 清空弹幕列表，但屏幕上已经出现的弹幕不会被清除。
         */
        this.cleanBuffer = function () {
            _bulletScreenBuffer.clean();
        };

        /**
         * 清空屏幕内容。
         * @description 清空屏幕内容。清空屏幕上已经出现的弹幕，不包括弹幕列表中的弹幕。
         */
        this.cleanScreen = function () {
            _realTimeBulletScreens.clean();
            _renderer.cleanScreen();
        };

        /**
         * 停止播放弹幕。
         * @description 停止播放弹幕。停止播放弹幕是指停止播放弹幕，默认[时间基准（options.clock）]{@link openBSE~BulletScreenStyle}归零，并[清空弹幕列表]{@link openBSE.BulletScreenEngine#cleanBulletScreenList}、[清空屏幕内容]{@link openBSE.BulletScreenEngine#cleanScreen}。
         */
        this.stop = function () {
            if (_playing) {
                this.pause();
            }
            this.cleanBuffer();
            this.cleanScreen();
            _pauseTime = 0;
            _startTime = null;
        };

        /**
         * 获取或设置弹幕可见性。
         * @private
         */
        Object.defineProperty(this, 'visibility', {
            get: function () {
                return renderer.getVisibility();
            },
            set: function (visibility) {
                if (visibility) _renderer.show();
                else _renderer.hide();
            }
        });

        /**
         * 获取渲染模式。
         * @private
         */
        Object.defineProperty(this, 'renderMode', {
            get: function () {
                return renderMode;
            }
        });

        /**
         * 获取播放状态。
         * @private
         */
        Object.defineProperty(this, 'playState', {
            get: function () {
                return _playing;
            }
        });

        /**
        * 获取调试信息。
        * @private
        */
        Object.defineProperty(this, 'debugInfo', {
            get: function () {
                return {
                    time: _playing ? _options.clock() : _pauseTime,
                    realTimeBulletScreenCount: _realTimeBulletScreens.length,
                    bufferBulletScreenCount: _bulletScreenBuffer.length,
                    delay: _delay,
                    delayBulletScreenCount: _delayBulletScreenCount,
                    fps: _playing ? Math.floor(_refreshRate * 1000) : 0 //帧频
                };
            }
        });

        //内部函数
        /**
         * 弹幕事件响应
         * @param {string} name - 事件名称
         * @param {object} realTimeBulletScreen - 实时弹幕对象
         * @param {object} e - 事件信息
         */
        function bulletScreenEventTrigger(name, realTimeBulletScreen, e) {
            if (typeof e.pageX === 'undefined' || e.pageX === null) {
                let doc = document.documentElement, body = document.body;
                e.pageX = e.clientX + (doc &amp;&amp; doc.scrollLeft || body &amp;&amp; body.scrollLeft || 0) - (doc &amp;&amp; doc.clientLeft || body &amp;&amp; body.clientLeft || 0);
                e.pageY = e.clientY + (doc &amp;&amp; doc.scrollTop || body &amp;&amp; body.scrollTop || 0) - (doc &amp;&amp; doc.clientTop || body &amp;&amp; body.clientTop || 0);
            }
            _event.trigger(name, {
                /**
                 * 获取引发事件的弹幕弹幕的数据
                 * @private
                 * @returns {openBSE~GeneralBulletScreen} 引发事件的弹幕的数据：一个 {@link openBSE~GeneralBulletScreen} 结构。（注意：不要试图与[添加弹幕]{@link openBSE.GeneralEngine#addBulletScreen}时创建的对象进行比较，这个对象是克隆得到的，并不相等。正确的方法是在添加弹幕时一并插入 id 等自定义字段来唯一标识一条弹幕。）
                 */
                getBulletScreen: () => Helper.clone(realTimeBulletScreen.bulletScreen),
                /**
                 * 设置引发事件的弹幕弹幕的数据
                 * @private
                 * @param {openBSE~GeneralBulletScreen} bulletScreen - 引发事件的弹幕的数据：一个 {@link openBSE~GeneralBulletScreen} 结构。设置此参数以便动态调整弹幕样式，但是一些参数在事件中修改无效，查看此结构的说明以了解详情。
                 * @param {boolean} [redraw=false] - 是否重绘弹幕：此参数在每次引发事件时的初始值为 false ，如果修改了 bulletScreen 中的值，此参数必须设为 true 。
                 */
                setBulletScreen: (bulletScreen, redraw = false) => {
                    if (typeof redraw != 'boolean') throw new TypeError(Resources.PARAMETERS_TYPE_ERROR);
                    let bulletScreenType = Helper.clone(_bulletScreenType);
                    bulletScreenType.style = _optionsType.defaultStyle;
                    realTimeBulletScreen.bulletScreen = Helper.setValues(bulletScreen, realTimeBulletScreen.bulletScreen, bulletScreenType); //设置值
                    if (redraw === true) _renderer.reCreatAndgetWidth(realTimeBulletScreen); //重新创建并绘制弹幕
                    if (!_playing &amp;&amp; redraw) _renderer.draw(); //非播放状态则重绘
                },
                /**
                 * 获取引发事件的弹幕的播放状态
                 * @private
                 * @returns {boolean} 取引发事件的弹幕是否在播放/移动：如果设置为 true 则该弹幕暂停，直到将此参数设为 false 或调用 {@link openBSE.GeneralEngine#playAllBulletScreens} 方法。
                 */
                getPlayState: () => !realTimeBulletScreen.pause,
                /**
                 * 设置引发事件的弹幕的播放状态
                 * @private
                 * @param {boolean} paly - 是否继续播放/移动引发事件的弹幕：读取此参数可判断这条弹幕是否处于暂停状态。
                 */
                setPlayState: (play) => {
                    if (typeof play != 'boolean') throw new TypeError(Resources.PARAMETERS_TYPE_ERROR);
                    realTimeBulletScreen.pause = !play;
                },
                screenX: e.screenX, screenY: e.screenY,
                pageX: e.pageX, pageY: e.pageY,
                clientX: e.clientX, clientY: e.clientY
            });
        }

        /**
         * 刷新弹幕函数
         * @private
         */
        function refresh() {
            let nowTime = new Date().getTime();
            if (_lastRefreshTime != null)
                _refreshRate = 1 / (nowTime - _lastRefreshTime);
            _lastRefreshTime = nowTime;
            addRealTimeBulletScreens();
            moveRealTimeBulletScreen();
            _renderer.draw(); //绘制弹幕
            if (_playing)
                _requestAnimationFrameHandel = requestAnimationFrame(refresh);
        }

        /**
         * 移动弹幕函数
         * @private
         */
        function moveRealTimeBulletScreen() {
            _realTimeBulletScreens.forEach((node) => {
                let realTimeBulletScreen = node.element;
                if (realTimeBulletScreen.pause) return; //暂停移动
                let nowTime = _options.clock();
                switch (realTimeBulletScreen.type) {
                    case GeneralType.rightToLeft:
                        if (realTimeBulletScreen.x > -realTimeBulletScreen.width) {
                            realTimeBulletScreen.x -= realTimeBulletScreen.bulletScreen.style.speed * _options.playSpeed / _refreshRate;
                        }
                        else {
                            _renderer.delete(realTimeBulletScreen);
                            return { remove: true };
                        }
                        break;
                    case GeneralType.leftToRight:
                        if (realTimeBulletScreen.x &lt; _elementSize.width) {
                            realTimeBulletScreen.x += realTimeBulletScreen.bulletScreen.style.speed * _options.playSpeed / _refreshRate;
                        }
                        else {
                            _renderer.delete(realTimeBulletScreen);
                            return { remove: true };
                        }
                        break;
                    case GeneralType.top:
                    case GeneralType.bottom:
                        if (realTimeBulletScreen.endTime &lt; nowTime) {
                            _renderer.delete(realTimeBulletScreen);
                            return { remove: true };
                        }
                        break;
                }
            }, true);
        }

        /**
         * 添加弹幕到实时弹幕列表
         * @private
         */
        function addRealTimeBulletScreens() {
            if (_realTimeBulletScreens.length === 0)
                _delay = 0;
            let times = Math.floor(_refreshRate * 2000);
            do {
                let node = _bulletScreenBuffer.pop(false, false);
                if (node === null) return;
                let bulletScreen = node.element;
                let nowTime = _options.clock();
                if (bulletScreen.startTime > nowTime) return;
                if (!_options.timeOutDiscard || !bulletScreen.canDiscard || bulletScreen.startTime > nowTime - Math.floor(1 / _refreshRate) * 60) {
                    bulletScreen.style = Helper.setValues(bulletScreen.style, _options.defaultStyle, _optionsType.defaultStyle); //填充默认样式
                    getRealTimeBulletScreen(nowTime, bulletScreen); //生成实时弹幕对象并添加到实时弹幕集合            
                }
                else _delayBulletScreenCount++;
                node.remove();
                times--;
            } while (_realTimeBulletScreens.length === 0 || times > 0);
        }

        /**
         * 生成实时弹幕对象
         * @private
         * @param {number} nowTime - 当前时间
         * @param {openBSE~BulletScreen} bulletScreen - 弹幕的链表节点
         */
        function getRealTimeBulletScreen(nowTime, bulletScreen) {
            _delay = nowTime - bulletScreen.startTime;
            let realTimeBulletScreen = {};
            realTimeBulletScreen.pause = false; //是否暂停移动
            realTimeBulletScreen.bulletScreen = bulletScreen;
            realTimeBulletScreen.startTime = nowTime; //弹幕头部进屏幕时间
            realTimeBulletScreen.size = bulletScreen.style.size; //字体大小：像素
            realTimeBulletScreen.type = bulletScreen.type; //弹幕类型
            realTimeBulletScreen.height = realTimeBulletScreen.size; //弹幕的高度：像素
            _renderer.creatAndgetWidth(realTimeBulletScreen); //创建弹幕元素并计算宽度
            switch (bulletScreen.type) {
                case GeneralType.rightToLeft:
                    realTimeBulletScreen.endTime = Math.round(nowTime + (_elementSize.width + realTimeBulletScreen.width) / (bulletScreen.style.speed * _options.playSpeed)); //弹幕尾部出屏幕的时间
                    realTimeBulletScreen.x = _elementSize.width; //弹幕初始X坐标
                    realTimeBulletScreen.y = _options.verticalInterval; //弹幕初始Y坐标
                    break;
                case GeneralType.leftToRight:
                    realTimeBulletScreen.endTime = Math.round(nowTime + (_elementSize.width + realTimeBulletScreen.width) / (bulletScreen.style.speed * _options.playSpeed)); //弹幕尾部出屏幕的时间
                    realTimeBulletScreen.x = -realTimeBulletScreen.width; //弹幕初始X坐标
                    realTimeBulletScreen.y = _options.verticalInterval; //弹幕初始Y坐标
                    break;
                case GeneralType.top:
                    realTimeBulletScreen.endTime = realTimeBulletScreen.startTime + bulletScreen.style.residenceTime * _options.playSpeed;
                    realTimeBulletScreen.x = Math.round((_elementSize.width - realTimeBulletScreen.width) / 2); //弹幕初始X坐标
                    realTimeBulletScreen.y = _options.verticalInterval; //弹幕初始Y坐标
                    break;
                case GeneralType.bottom:
                    realTimeBulletScreen.endTime = realTimeBulletScreen.startTime + bulletScreen.style.residenceTime * _options.playSpeed;
                    realTimeBulletScreen.x = Math.round((_elementSize.width - realTimeBulletScreen.width) / 2); //弹幕初始X坐标
                    realTimeBulletScreen.y = -_options.verticalInterval - realTimeBulletScreen.height; //弹幕初始Y坐标
                    break;
            }

            let newNode = new LinkedList.node(realTimeBulletScreen);
            if (bulletScreen.type === GeneralType.top || bulletScreen.type === GeneralType.bottom) {
                _realTimeBulletScreens.forEach((node) => {
                    let nextrealTimeBulletScreen = node.element;
                    //弹幕不在流中，是固定弹幕
                    if (nextrealTimeBulletScreen.bulletScreen.type != bulletScreen.type)
                        return; //不是同一种类型的弹幕
                    if (bulletScreen.type === GeneralType.top) {
                        //如果新弹幕在当前弹幕上方且未与当前弹幕重叠
                        if (realTimeBulletScreen.y + realTimeBulletScreen.height &lt; nextrealTimeBulletScreen.y) {
                            setActualY(realTimeBulletScreen);
                            return { add: { addToUp: true, node: newNode }, stop: true };
                        }
                        //如果上一条弹幕的消失时间小于当前弹幕的出现时间
                        if (nextrealTimeBulletScreen.endTime &lt; nowTime)
                            realTimeBulletScreen.y = nextrealTimeBulletScreen.y;
                        else
                            realTimeBulletScreen.y = nextrealTimeBulletScreen.y + nextrealTimeBulletScreen.height + _options.verticalInterval;
                    }
                    else {
                        //如果新弹幕在当前弹幕下方且未与当前弹幕重叠
                        if (realTimeBulletScreen.y > nextrealTimeBulletScreen.y + nextrealTimeBulletScreen.height) {
                            setActualY(realTimeBulletScreen);
                            return { add: { addToUp: true, node: newNode }, stop: true };
                        }
                        //如果上一条弹幕的消失时间小于当前弹幕的出现时间
                        if (nextrealTimeBulletScreen.endTime &lt; nowTime)
                            realTimeBulletScreen.y = nextrealTimeBulletScreen.y;
                        else
                            realTimeBulletScreen.y = nextrealTimeBulletScreen.y - realTimeBulletScreen.height - _options.verticalInterval;
                    }
                }, true);
            }
            else {
                //当前弹幕经过一个点需要的总时长
                let realTimeBulletScreenWidthTime = realTimeBulletScreen.width / (bulletScreen.style.speed * _options.playSpeed);
                _realTimeBulletScreens.forEach((node) => {
                    let nextrealTimeBulletScreen = node.element;
                    //弹幕在流中，是移动弹幕
                    if (nextrealTimeBulletScreen.bulletScreen.type === GeneralType.top || nextrealTimeBulletScreen.bulletScreen.type === GeneralType.bottom)
                        return; //弹幕不在流中，为固定弹幕
                    //如果新弹幕在当前弹幕上方且未与当前弹幕重叠
                    if (realTimeBulletScreen.y + realTimeBulletScreen.height &lt; nextrealTimeBulletScreen.y) {
                        setActualY(realTimeBulletScreen);
                        return { add: { addToUp: true, node: newNode }, stop: true };
                    }
                    //上一条弹幕经过一个点需要的总时长
                    let nextrealTimeBulletScreenWidthTime = nextrealTimeBulletScreen.width / (nextrealTimeBulletScreen.bulletScreen.style.speed * _options.playSpeed);
                    //如果上一条弹幕的消失时间小于当前弹幕的出现时间
                    if (nextrealTimeBulletScreen.startTime + nextrealTimeBulletScreenWidthTime >= nowTime || //如果上一条弹幕的头进入了，但是尾还没进入
                        nextrealTimeBulletScreen.endTime >= realTimeBulletScreen.endTime - realTimeBulletScreenWidthTime) //如果当前弹幕头出去了，上一条弹幕尾还没出去
                        realTimeBulletScreen.y = nextrealTimeBulletScreen.y + nextrealTimeBulletScreen.height + _options.verticalInterval;
                    else
                        realTimeBulletScreen.y = nextrealTimeBulletScreen.y;
                }, true);
            }
            if (newNode.linkedList === null) {
                setActualY(realTimeBulletScreen);
                _realTimeBulletScreens.push(newNode, false);
            }
        }

        /**
         * 设置真实的Y坐标
         * @private
         * @param {object} realTimeBulletScreen - 实时弹幕事件
         * @returns {object} 实时弹幕事件
         */
        function setActualY(realTimeBulletScreen) {
            let bulletScreen = realTimeBulletScreen.bulletScreen;
            if (
                bulletScreen.type === GeneralType.leftToRight ||
                bulletScreen.type === GeneralType.rightToLeft ||
                bulletScreen.type === GeneralType.top
            ) {
                realTimeBulletScreen.actualY = realTimeBulletScreen.y % (_elementSize.height - realTimeBulletScreen.height);
            }
            else if (bulletScreen.type === GeneralType.bottom) {
                realTimeBulletScreen.actualY = _elementSize.height + realTimeBulletScreen.y % _elementSize.height;
            }
        }

        /**
         * 设置尺寸
         * @private
         */
        function setSize() {
            let devicePixelRatio = Helper.getDevicePixelRatio();
            if (_oldDevicePixelRatio != devicePixelRatio ||
                _oldClientWidth != element.clientWidth ||
                _oldClientHeight != element.clientHeight ||
                _oldScaling != _options.scaling) {
                _oldScaling = _options.scaling;
                _elementSize.width = element.clientWidth / _options.scaling;
                _elementSize.height = element.clientHeight / _options.scaling;
                _oldClientWidth = element.clientWidth;
                _oldClientHeight = element.clientHeight;
                _oldDevicePixelRatio = devicePixelRatio;
                _renderer.setSize();
                if (!_playing) _renderer.draw(); //非播放状态则重绘
            }
        }
    }
}

export default GeneralEngine</code></pre>
        </article>
    </section>




    
    
</div>

<br class="clear">

<footer>
    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.5</a> on Wed Sep 09 2020 06:20:13 GMT+0000 (Coordinated Universal Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>

<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>

<script src="scripts/search.js" defer></script>


<script src="scripts/collapse.js" defer></script>


</body>
</html>
